using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.Serialization;

namespace Sio.Mdm.Utils.Collections
{    
    /// <summary>
    ///    Represents a strongly typed list of objects that can be accessed by index.
    ///    Provides methods to search, sort, and manipulate lists.
    /// </summary>
    /// <typeparam name="T">Type of list collection</typeparam>        
    [Serializable]
    [DebuggerDisplay("Count = {Count}")]
    [CLSCompliant(true)]
    public class BaseList<T> : List<T>, IDisposable
    {
        #region Declarations
        /// <summary>
        /// Declares possible dump types
        /// </summary>
        public enum DumpType : short
        {
            /// <summary>
            /// Return simple information on collection
            /// </summary>
            Simple,
            /// <summary>
            /// Return extended information on collection with items
            /// </summary>
            Diagnostic,
        }
        #endregion

        #region Fields
        private bool IsDisposable = typeof(IDisposable).IsAssignableFrom(typeof(T));
        #endregion

        #region Construction
        /// <summary>
        /// Initialize new list with default capacity
        /// </summary>
        public BaseList()
        {
        }
        /// <summary>
        /// Initialize new list with inserted items from passed enumerable
        /// </summary>
        /// <param name="collection">Items to be inserted intiali to collection</param>
        public BaseList(IEnumerable<T> collection)
            : base(collection)
        {   
        }
        /// <summary>
        /// Initialize new list with specified capacity
        /// </summary>
        /// <param name="capacity">Initial capacity of list</param>
        public BaseList(int capacity)
            : base(capacity)
        {   
        }
        /// <summary>
        /// Finalizer
        /// </summary>
        ~BaseList()
        {
            Dispose(false);
        }
        #endregion

        #region Properties

        #region Public

        #region IsDisposed
        private bool _isDisposed;
        /// <summary>
        /// Get wheater dictionary was disposed
        /// </summary>
        public bool IsDisposed
        {
            get
            {
                return _isDisposed;
            }
        }
        #endregion

        #region First
        /// <summary>
        /// Get first item in list
        /// </summary>
        public T First
        {
            get
            {
                return Count > 0 ? this[0] : default(T);
            }
        }
        #endregion

        #region Last
        /// <summary>
        /// Get last item in list
        /// </summary>
        public T Last
        {
            get
            {
                return Count > 0 ? this[Count - 1] : default(T);
            }
        }
        #endregion

        #endregion

        #endregion

        #region Methods

        #region Public

        #region Indexer
        /// <summary>
        /// Gets or sets the element at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index of the element to get or set.</param>
        /// <returns>The element at the specified index.</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">
        /// index is less than 0.
        /// -or-
        /// index is equal to or greater than System.Collections.Generic.List&lt;T&gt;.Count.
        /// </exception>
        public new T this[int index]
        {
            get
            {
                if (_isDisposed)
                {
                    throw new ObjectDisposedException(GetType().ToString());
                }

                return base[index];
            }
            set
            {
                if (_isDisposed)
                {
                    throw new ObjectDisposedException(GetType().ToString());
                }

                if (!OnBeforeSet(index, value))
                {
                    return;
                }

                base[index] = value;
                OnAfterSet(index, value);
            }
        } 
        #endregion

        #region Add
        /// <summary>
        /// Adds an object to the end of the System.Collections.Generic.List&lt;T&gt;.
        /// </summary>
        /// <param name="item">
        ///    The object to be added to the end of the System.Collections.Generic.List&lt;T&gt;.
        ///     The value can be null for reference types.
        /// </param>
        public new void Add(T item)
        {
            Insert(Count, item);
        } 
        #endregion

        #region AddRange
        /// <summary>
        /// Adds the elements of the specified collection to the end of the System.Collections.Generic.List&lt;T&gt;.
        /// </summary>
        /// <param name="collection">
        /// The collection whose elements should be added to the end of the System.Collections.Generic.List&lt;T&gt;.
        /// The collection itself cannot be null, but it can contain elements that are
        /// null, if type T is a reference type.
        /// </param>        
        /// <exception cref="System.ArgumentOutOfRangeException">
        ///   collection is null.
        /// </exception>
        public new void AddRange(IEnumerable<T> collection)
        {
            int index = 0;

            foreach (T item in collection)
            {
                Insert(index++, item);
            }
        } 
        #endregion

        #region Insert
        /// <summary>
        /// Inserts an element into the System.Collections.Generic.List&lt;T&gt; at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index at which item should be inserted.</param>
        /// <param name="item">The object to insert. The value can be null for reference types.</param>
        /// <exception cref="System.ArgumentOutOfRangeException">
        /// index is less than 0.
        /// -or-
        /// index is greater than System.Collections.Generic.List&lt;T&gt;.Count.
        /// </exception>
        public new void Insert(int index, T item)
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException(GetType().ToString());
            }

            if (!OnBeforeInsert(index, item))
            {
                return;
            }

            base.Insert(index, item);
            OnAfterInsert(index, item);
        } 
        #endregion

        #region Remove
        /// <summary>
        /// Removes the first occurrence of a specific object from the System.Collections.Generic.List&lt;T&gt;.
        /// </summary>
        /// <param name="item">The object to remove from the System.Collections.Generic.List&lt;T&gt;. The value can be null for reference types.
        /// </param>
        /// <returns>true if item is successfully removed; otherwise, false.</returns>
        public new bool Remove(T item)
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException(GetType().ToString());
            }

            int index = IndexOf(item);

            if (!OnBeforeRemove(index, item))
            {
                return false;
            }

            bool result = base.Remove(item);
            OnAfterRemove(item);

            return result;
        } 
        #endregion

        #region RemoveAt
        /// <summary>
        /// Removes the element at the specified index of the System.Collections.Generic.List&lt;T&gt;.
        /// </summary>
        /// <param name="index">The zero-based index of the element to remove.</param>
        /// <exception cref="System.ArgumentOutOfRangeException">
        /// index is less than 0.
        /// -or-
        /// index is equal to or greater than System.Collections.Generic.List&lt;T%gt;.Count.
        /// </exception>
        public new void RemoveAt(int index)
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException(GetType().ToString());
            }

            T item = base[index];

            if (!OnBeforeRemove(index, item))
            {
                return;
            }

            base.RemoveAt(index);
            OnAfterRemove(item);
        } 
        #endregion

        #region RemoveAll
        /// <summary>
        /// Removes the all the elements that match the conditions defined by the specified predicate.
        /// </summary>
        /// <param name="match">
        ///    The System.Predicate&lt;T&gt; delegate that defines the conditions of the elements
        ///    to remove.
        /// </param>
        /// <returns>
        /// The number of elements removed from the System.Collections.Generic.List&lt;T&gt;
        /// </returns>
        public new int RemoveAll(Predicate<T> match)
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException(GetType().ToString());
            }

            int count = Count;
            int removed = 0;

            for (int i = 0; i < count; i++)
            {
                T item = base[i];

                if (OnBeforeRemove(i, item) && match(item))
                {
                    base.RemoveAt(i);
                    OnAfterRemove(item);
                    removed++;
                }
            }

            return removed;
        } 
        #endregion

        #region RemoveRange
        /// <summary>
        /// Removes a range of elements 
        /// </summary>
        /// <param name="index">The zero-based starting index of the range of elements to remove.</param>
        /// <param name="count">The number of elements to remove.</param>
        public new void RemoveRange(int index, int count)
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException(GetType().ToString());
            }

            for (int i = index; i < count; i++)
            {
                T item = base[i];

                if (OnBeforeRemove(i, item))
                {
                    base.RemoveAt(i);
                    OnAfterRemove(item);
                }
            }
        } 
        #endregion

        #region Clear
        /// <summary>
        /// Removes all elements from the System.Collections.Generic.List&lt;T&gt;.
        /// </summary>
        public new void Clear()
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException(GetType().ToString());
            }

            if (!OnBeforeClear())
            {
                return;
            }

            base.Clear();
            OnAfterClear();
        } 
        #endregion

        #region ToString
        /// <summary>
        /// Return human readable representation of collection
        /// </summary>
        /// <returns>Human readable string of collection</returns>
        public override string ToString()
        {
            return String.Format("Count = {0}", Count);
        }
        /// <summary>
        /// Return human readable string representation collection
        /// </summary>
        /// <param name="type">Level of detail to include</param>
        /// <returns>String with details on collection</returns>
        public string ToString(DumpType type)
        {
            StringBuilder builder = new StringBuilder();
            int index = 0;

            if (type == DumpType.Diagnostic)
            {
                builder.AppendFormat("Count = {0}, Type = {1}\n", Count, GetType());
            }

            foreach (T item in this)
            {
                switch (type)
                {
                    case DumpType.Simple:
                        builder.AppendFormat("{0} = {1}\n", index++, item.ToString());
                        break;

                    case DumpType.Diagnostic:
                        builder.AppendFormat("item {0} = {1}\n", index++, item.ToString());
                        break;
                }
            }

            return builder.ToString();
        }
        #endregion

        #region IDisposable Members
        /// <summary>
        /// Dispose resources allocated within instance
        /// </summary>
        public void Dispose()
        {
            if (_isDisposed)
            {
                return;
            }

            if (IsDisposable)
            {
                foreach (T item in this)
                {
                    (item as IDisposable).Dispose();
                }

                Clear();
            }

            Dispose(true);
            _isDisposed = true;
        }
        #endregion

        #endregion

        #region Protected

        #region OnBeforeInsert, OnAfterInsert
        /// <summary>
        /// Invokes <see cref="BeforeInsert"/> event
        /// </summary>
        /// <param name="index">Index where item should be inserted</param>
        /// <param name="value">Value that is to be added</param>
        /// <returns>True if item should be added</returns>
        protected virtual bool OnBeforeInsert(int index, T value)
        {
            if (BeforeInsert != null)
            {
                ItemCancelEventArgs<T> e = new ItemCancelEventArgs<T>(value, index);
                BeforeInsert(this, e);

                return !e.Cancel;
            }

            return true;
        }
        /// <summary>
        /// Invokes <see cref="AfterInsert"/> event
        /// </summary>
        /// <param name="value">Value added</param>
        /// <param name="index">Index of value</param>
        protected virtual void OnAfterInsert(int index, T value)
        {
            if (AfterInsert != null)
            {
                ItemEventArgs<T> args = new ItemEventArgs<T>(value, index);
                AfterInsert(this, args);
            }
        }
        #endregion

        #region OnBeforeRemove, OnAfterRemove
        /// <summary>
        /// Invokes <see cref="BeforeRemove"/> event
        /// </summary>
        /// <param name="value">Value to be removed</param>
        /// <param name="index">Index in list</param>
        /// <returns>True if item should be removed</returns>
        protected virtual bool OnBeforeRemove(int index, T value)
        {
            if (BeforeRemove != null)
            {
                ItemCancelEventArgs<T> e = new ItemCancelEventArgs<T>(value, index);
                BeforeRemove(this, e);

                return !e.Cancel;
            }

            return true;
        }
        /// <summary>
        /// Invokes <see cref="AfterRemove"/> event
        /// </summary>
        /// <param name="value">Removed value</param>
        protected virtual void OnAfterRemove(T value)
        {
            if (AfterRemove != null)
            {
                ItemEventArgs<T> args = new ItemEventArgs<T>(value, -1);
                AfterRemove(this, args);
            }
        }
        #endregion

        #region OnBeforeClear, OnAfterCler
        /// <summary>
        /// Invokes <see cref="BeforeClear"/> event
        /// </summary>
        /// <returns>True if list can be cleared</returns>
        protected virtual bool OnBeforeClear()
        {
            if (BeforeClear != null)
            {
                CancelEventArgs e = new CancelEventArgs();
                BeforeClear(this, e);

                return !e.Cancel;
            }

            return true;        
        }
        /// <summary>
        /// Invokes <see cref="AfterClear"/> event
        /// </summary>
        protected virtual void OnAfterClear()
        {
            if (AfterClear != null)
            {
                AfterClear(this, EventArgs.Empty);
            }
        }
        #endregion

        #region OnBeforeSet, OnAfterSet
        /// <summary>
        /// Invokes <see cref="BeforeSet"/> event
        /// </summary>
        /// <param name="value">Item that will be set</param>
        /// <param name="index">Index that will be replaced</param>
        /// <returns>True if setting can proceed</returns>
        protected virtual bool OnBeforeSet(int index, T value)
        {
            if (BeforeSet != null)
            {
                ItemCancelEventArgs<T> e = new ItemCancelEventArgs<T>(value, index);
                BeforeSet(this, e);

                return !e.Cancel;
            }

            return true;
        }
        /// <summary>
        /// Invokes <see cref="AfterSet"/> event
        /// </summary>
        /// <param name="value">Value that was set</param>
        /// <param name="index">Index of value</param>
        protected virtual void OnAfterSet(int index, T value)
        {
            if (AfterSet != null)
            {
                ItemEventArgs<T> args = new ItemEventArgs<T>(value, index);
                AfterSet(this, args);
            }
        }
        #endregion

        #region Dispose
        /// <summary>
        /// Dispoe resources used by instance
        /// </summary>
        /// <param name="disposing">True to dispose managed resources</param>
        protected virtual void Dispose(bool disposing)
        {
        } 
        #endregion

        #endregion

        #endregion

        #region Events

        #region Public

        #region BeforeInsert, AfterInsert
        /// <summary>
        /// Fires when items is about to be added
        /// </summary>
        public event EventHandler<ItemCancelEventArgs<T>> BeforeInsert;
        /// <summary>
        /// Fires after item was added
        /// </summary>
        public event EventHandler<ItemEventArgs<T>> AfterInsert;
        #endregion

        #region BeforeRemove, AfterRemove
        /// <summary>
        /// Fires before item is to be removed
        /// </summary>
        public event EventHandler<ItemCancelEventArgs<T>> BeforeRemove;
        /// <summary>
        /// Fires after item was remove from collection
        /// </summary>
        public event EventHandler<ItemEventArgs<T>> AfterRemove;
        #endregion

        #region BeforeClear, AfterClear
        /// <summary>
        /// Fires before collection is to be cleared
        /// </summary>
        public event EventHandler<CancelEventArgs> BeforeClear;
        /// <summary>
        /// Fires after collection was cleared
        /// </summary>
        public event EventHandler<EventArgs> AfterClear;
        #endregion

        #region BeforeSet, AfterSet
        /// <summary>
        /// Fires before item is set
        /// </summary>
        public event EventHandler<ItemCancelEventArgs<T>> BeforeSet;
        /// <summary>
        /// Fires after item was set
        /// </summary>
        public event EventHandler<ItemEventArgs<T>> AfterSet;
        #endregion

        #endregion

        #endregion
    }
}
